﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using System.Collections;

namespace xnaMugen.Combat
{
	internal class EntityCollection : EngineObject, IEnumerable<Entity>
	{
		public struct Enumerator : IEnumerator<Entity>
		{
			public Enumerator(EntityCollection collection)
			{
				if (collection == null) throw new ArgumentNullException(nameof(collection));

				m_collection = collection;
				m_current = null;
				m_index = 0;
			}

			public Enumerator GetEnumerator()
			{
				return new Enumerator(m_collection);
			}

			public void Dispose()
			{
			}

			public bool MoveNext()
			{
				if (m_collection == null) return false;

				var firstcount = m_collection.m_entities.Count;
				var totalcount = firstcount + m_collection.m_addlist.Count;

				for (; m_index < totalcount; ++m_index)
				{
					m_current = m_index < firstcount ? m_collection.m_entities[m_index] : m_collection.m_addlist[m_index - firstcount];

					if (m_collection.m_removelist.Contains(m_current) == false)
					{
						++m_index;
						return true;
					}
				}

				m_current = null;
				return false;
			}

			public void Reset()
			{
				m_current = null;
				m_index = 0;
			}

			public Entity Current => m_current;

			object IEnumerator.Current => m_current;

			#region Fields

			[DebuggerBrowsable(DebuggerBrowsableState.Never)]
			private readonly EntityCollection m_collection;

			[DebuggerBrowsable(DebuggerBrowsableState.Never)]
			private Entity m_current;

			[DebuggerBrowsable(DebuggerBrowsableState.Never)]
			private int m_index;

			#endregion
		}

		public EntityCollection(FightEngine fightengine)
			: base(fightengine)
		{
			m_entities = new List<Entity>();
			m_addlist = new List<Entity>();
			m_removelist = new List<Entity>();
			m_tempqueue = new List<Entity>();
			m_drawordercomparer = DrawOrderComparer;
			m_updateordercomparer = UpdateOrderComparer;
			m_removecheck = DrawRemoveCheck;
			m_inupdate = false;
		}

		public bool Contains(Entity entity)
		{
			if (entity == null) throw new ArgumentNullException(nameof(entity));

			foreach (var e in this)
			{
				if (ReferenceEquals(e, entity)) return true;
			}

			return false;
		}

		public void Add(Entity entity)
		{
			if (entity == null) throw new ArgumentNullException(nameof(entity));
			if (Contains(entity)) throw new ArgumentException("Entity is already part of collection");

			if (m_inupdate == false)
			{
				m_entities.Add(entity);
			}
			else
			{
				m_addlist.Add(entity);
			}

			if (entity is Helper)
			{
				var helper = (Helper)entity;

				List<Helper> helpers;
				if (helper.BasePlayer.Helpers.TryGetValue(helper.Data.HelperId, out helpers) == false)
				{
					helpers = new List<Helper>();
					helper.BasePlayer.Helpers.Add(helper.Data.HelperId, helpers);
				}

				helpers.Add(helper);
			}

			if (entity is Explod)
			{
				var explod = (Explod)entity;

				List<Explod> explods;
				if (explod.Creator.Explods.TryGetValue(explod.Data.Id, out explods) == false)
				{
					explods = new List<Explod>();
					explod.Creator.Explods.Add(explod.Data.Id, explods);
				}

				explods.Add(explod);
			}
		}

		public void Remove(Entity entity)
		{
			if (entity == null) throw new ArgumentNullException(nameof(entity));
			if (Contains(entity) == false) throw new ArgumentException("Entity is not part of collection");
			if (m_removelist.Contains(entity)) throw new ArgumentException("Entity is already set to be removed from collection");

			if (m_inupdate == false)
			{
				m_entities.Remove(entity);
				m_addlist.Remove(entity);
			}
			else
			{
				m_removelist.Add(entity);
			}

			if (entity is Helper)
			{
				var helper = (Helper)entity;
				helper.BasePlayer.Helpers[helper.Data.HelperId].Remove(helper);
			}

			if (entity is Explod)
			{
				var explod = (Explod)entity;
				explod.Creator.Explods[explod.Data.Id].Remove(explod);
			}
		}

		public void Clear()
		{
			m_addlist.Clear();
			m_entities.Clear();
			m_removelist.Clear();
		}

		private void AddEntities()
		{
			//foreach (var entity in m_addlist)
			//{
			//	m_entities.Add(entity);
			//}

			for (int i=0;i < m_addlist.Count;i++)
			{
				m_entities.Add(m_addlist[i]);
			}

			m_addlist.Clear();
		}

		private void RemoveEnities()
		{
			//foreach (var entity in m_removelist)
			//{
			//	m_entities.Remove(entity);
			//	m_addlist.Remove(entity);
			//}

			for (int i = 0; i < m_removelist.Count; i++)
			{
				m_entities.Remove(m_removelist[i]);
				m_addlist.Remove(m_removelist[i]);
			}

			m_removelist.Clear();
		}

		private void RemoveCheck()
		{
            foreach (var entity in this)
            {
                if (entity.RemoveCheck()) Remove(entity);
            }

        }

		public void CountEntities(out int players, out int helpers, out int explods, out int projectiles)
		{
			players = 0;
			helpers = 0;
			explods = 0;
			projectiles = 0;

			foreach (var entity in this)
			{
				if (entity is Player) ++players;
				else if (entity is Helper) ++helpers;
				else if (entity is Explod) ++explods;
				else if (entity is Projectile) ++projectiles;
			}
		}

		public void Update(GameTime time)
		{
			m_inupdate = true;

			AddEntities();
			RemoveCheck();
			RemoveEnities();

			m_tempqueue.Clear();

			foreach (var entity in this)
			{
				if (Engine.SuperPause.IsPaused(entity) || Engine.Pause.IsPaused(entity)) continue;
				m_tempqueue.Add(entity);
			}

			RunEntityUpdates(m_tempqueue);
			m_tempqueue.Clear();

			while (m_addlist.Count > 0)
			{
				//foreach (var entity in m_addlist)
				//{
				//	if (Engine.SuperPause.IsPaused(entity) || Engine.Pause.IsPaused(entity)) continue;
				//	m_tempqueue.Add(entity);
				//}

				for (int i = 0; i < m_addlist.Count; i++)
				{
					if (Engine.SuperPause.IsPaused(m_addlist[i]) || Engine.Pause.IsPaused(m_addlist[i])) continue;
					m_tempqueue.Add(m_addlist[i]);
				}

				AddEntities();

				RunEntityUpdates(m_tempqueue);
			}

			RemoveCheck();
			RemoveEnities();

			m_inupdate = false;
		}

		private void RunEntityUpdates(List<Entity> entities)
		{
			if (entities == null) throw new ArgumentNullException(nameof(entities));

			entities.Sort(m_updateordercomparer);

			////foreach (var entity in entities) entity.CleanUp();
			//for  (int i=0;i< entities.Count;i++) entities[i].CleanUp();

			////foreach (var entity in entities) entity.UpdateInput();
			//for (int i = 0; i < entities.Count; i++) entities[i].UpdateInput();


			////foreach (var entity in entities) entity.UpdateAnimations();
			//for (int i = 0; i < entities.Count; i++) entities[i].UpdateAnimations();


			////foreach (var entity in entities) entity.UpdateState();
			//for (int i = 0; i < entities.Count; i++) entities[i].UpdateState();


			////foreach (var entity in entities) entity.UpdateAfterImages();
			//for (int i = 0; i < entities.Count; i++) entities[i].UpdateAfterImages();


			////foreach (var entity in entities) entity.UpdatePhsyics();
			//for (int i = 0; i < entities.Count; i++) entities[i].UpdatePhsyics();

			for (int i = 0; i < entities.Count; i++)
            {
				entities[i].CleanUp();
				entities[i].UpdateInput();
				entities[i].UpdateAnimations();
				entities[i].UpdateState();
				entities[i].UpdateAfterImages();
				entities[i].UpdatePhsyics();
			}

				entities.Clear();
		}

		public void Draw(bool debug)
		{
			m_tempqueue.Clear();
			foreach (var entity in this)
			{
				if (entity.RemoveCheck()) continue;
				m_tempqueue.Add(entity);
			}

			m_tempqueue.Sort(m_drawordercomparer);

			var savedcamerashift = Engine.GetSubSystem<Video.VideoSystem>().CameraShift;

			Engine.GetSubSystem<Video.VideoSystem>().CameraShift = Engine.Camera.Location * -1;

			//foreach (var entity in m_tempqueue) entity.Draw();
			for (int i = 0; i < m_tempqueue.Count; i++) m_tempqueue[i].Draw();

			if (debug)
			{
				//foreach (var entity in m_tempqueue) entity.DebugDraw();
				for (int i = 0; i < m_tempqueue.Count; i++) m_tempqueue[i].DebugDraw();
			}

			Engine.GetSubSystem<Video.VideoSystem>().CameraShift = savedcamerashift;

			m_tempqueue.Clear();
		}

		public Enumerator GetEnumerator()
		{
			return new Enumerator(this);
		}

		IEnumerator<Entity> IEnumerable<Entity>.GetEnumerator()
		{
			return GetEnumerator();
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		private int UpdateOrderComparer(Entity lhs, Entity rhs)
		{
			if (lhs == null) throw new ArgumentNullException(nameof(lhs));
			if (rhs == null) throw new ArgumentNullException(nameof(rhs));

			if (lhs == rhs) return 0;

			var order = lhs.UpdateOrder - rhs.UpdateOrder;
			if (order != 0) return order;

			if (lhs is Character && rhs is Character)
			{
				var clhs = lhs as Character;
				var crhs = rhs as Character;

				var tlhs = clhs.MoveType == MoveType.BeingHit;
				var trhs = crhs.MoveType == MoveType.BeingHit;

				if (tlhs && trhs == false)
				{
					return 1;
				}

				if (tlhs == false && trhs)
				{
					return -1;
				}
			}

			return m_entities.IndexOf(lhs) - m_entities.IndexOf(rhs);
		}

		private int DrawOrderComparer(Entity lhs, Entity rhs)
		{
			if (lhs == null) throw new ArgumentNullException(nameof(lhs));
			if (rhs == null) throw new ArgumentNullException(nameof(rhs));

			if (lhs == rhs) return 0;

			var order = lhs.DrawOrder - rhs.DrawOrder;
			if (order != 0) return order;

			if (lhs is Player && rhs is Player == false) return 1;
			if (rhs is Player && lhs is Player == false) return -1;

			return m_entities.IndexOf(lhs) - m_entities.IndexOf(rhs);
		}

		private bool DrawRemoveCheck(Entity entity)
		{
			if (entity == null) throw new ArgumentNullException(nameof(entity));

			return m_removelist.Contains(entity) || Engine.EnvironmentColor.IsHidden(entity);
		}

		#region Fields

		private readonly List<Entity> m_entities;

		private readonly List<Entity> m_addlist;

		private readonly List<Entity> m_removelist;

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private readonly List<Entity> m_tempqueue;

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private readonly Comparison<Entity> m_updateordercomparer;

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private readonly Comparison<Entity> m_drawordercomparer;

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private readonly Predicate<Entity> m_removecheck;

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		private bool m_inupdate;

		#endregion
	}
}